﻿using OpenTK.Graphics.OpenGL4;
using System;
using System.IO;
using OpenTK;
using System.Collections.Generic;

namespace Painter3D.RenderSys
{
    public class Shader : IDisposable
    {
        /// <summary>
        /// 着色器的名称
        /// </summary>
        public readonly string shaderName;
        /// <summary>
        /// 提取出的着色器信息
        /// </summary>
        public readonly ShaderInfo info;
        private readonly int Handle;

        public Shader(string name, string vertShaderSource, string fragShaderSource)
        {
            this.shaderName = name;
            //编译着色器程序
            Handle = CompileShader(vertShaderSource, fragShaderSource);
            //编译无问题后进行信息提取
            info = new ShaderInfo(vertShaderSource, fragShaderSource, this);
            //设置材质单元绑定信息
            SetSamplerUnitByInfo();
            //绑定系统全局变量块
            foreach (var blockInfo in info.blockInfos)
            {
                BindUniformToPoint(blockInfo.blockName, blockInfo.bindingPoint);
            }
            //添加到管理器
            ShaderManager.Add(this);
        }
        /// <summary>
        /// 从独立的着色器文件构建着色器程序
        /// </summary>
        /// <param name="vertShaderPath">顶点着色器路径 后缀vert</param>
        /// <param name="fragShaderPath">片段着色器路径 后缀frag</param>
        [Obsolete]
        static public void Load(string vertShaderPath, string fragShaderPath, string name = null)
        {
            string shaderName;
            if (name == null)
            {
                shaderName = vertShaderPath.Substring(vertShaderPath.LastIndexOf('\\') + 1) + fragShaderPath.Substring(fragShaderPath.LastIndexOf('\\') + 1);
            }
            else
            {
                shaderName = name;
            }
            string vertShaderSource;
            using (StreamReader reader = new StreamReader(vertShaderPath))
            {
                vertShaderSource = reader.ReadToEnd();
            }
            string fragShaderSource;
            using (StreamReader reader = new StreamReader(fragShaderPath))
            {
                fragShaderSource = reader.ReadToEnd();
            }
            new Shader(shaderName, vertShaderSource, fragShaderSource);
        }
        /// <summary>
        /// 从整合的着色器文件构建着色器程序
        /// </summary>
        /// <param name="vertShaderPath">着色器文件 后缀glsl</param>
        static public void Load(string ShaderPath)
        {
            string shaderName = ShaderPath.Substring(ShaderPath.LastIndexOf('\\') + 1);
            string vertShaderSource;
            string fragShaderSource;
            using (StreamReader reader = new StreamReader(ShaderPath))
            {
                string shaderSource = reader.ReadToEnd();
                int vertStartIndex = shaderSource.IndexOf(@"//vert");
                int fragStartIndex = shaderSource.IndexOf(@"//frag");
                string globalSource = shaderSource.Substring(0, vertStartIndex);
                vertShaderSource = globalSource + shaderSource.Substring(vertStartIndex + 6, fragStartIndex - vertStartIndex - 6);
                fragShaderSource = globalSource + shaderSource.Substring(fragStartIndex + 6);
            }
            new Shader(shaderName, vertShaderSource, fragShaderSource);
        }

        /// <summary>
        /// 编译着色器
        /// </summary>
        /// <param name="vertShaderSource">顶点着色器源码</param>
        /// <param name="fragShaderSource">片段着色器源码</param>
        /// <returns>着色器程序句柄</returns>
        private int CompileShader(string vertShaderSource, string fragShaderSource)
        {
            int Handle;
            int vertShader = GL.CreateShader(ShaderType.VertexShader);
            GL.ShaderSource(vertShader, vertShaderSource);
            int fragShader = GL.CreateShader(ShaderType.FragmentShader);
            GL.ShaderSource(fragShader, fragShaderSource);

            GL.CompileShader(vertShader);
            string infoLog = GL.GetShaderInfoLog(vertShader);
            if (infoLog != String.Empty)
                Log.LogOut("顶点着色器错误:" + vertShaderSource + "\n info:" + infoLog, "shaderErrorVert" + ToString());
            GL.CompileShader(fragShader);
            infoLog = GL.GetShaderInfoLog(fragShader);
            if (infoLog != String.Empty)
                Log.LogOut("着色器错误:" + fragShaderSource + "\n info:" + infoLog, "shaderErrorFrag" + ToString());
            Handle = GL.CreateProgram();
            GL.AttachShader(Handle, vertShader);
            GL.AttachShader(Handle, fragShader);
            GL.LinkProgram(Handle);

            GL.DetachShader(Handle, vertShader);
            GL.DetachShader(Handle, fragShader);
            GL.DeleteShader(vertShader);
            GL.DeleteShader(fragShader);
            return Handle;
        }

        public void Use()
        {
            GL.UseProgram(Handle);
        }
        /// <summary>
        /// 设置uniform的委托列表
        /// </summary>
        static private Dictionary<Type, Delegate> dic = new Dictionary<Type, Delegate>()
            {
                { typeof(Matrix4),new Action<int,Matrix4>((index,matrix)=>GL.UniformMatrix4(index,false,ref matrix))},
                { typeof(Vector4),new Action<int,Vector4>((index,vector4)=>GL.Uniform4(index,ref vector4))},
                { typeof(Vector3),new Action<int,Vector3>((index,vector3)=>GL.Uniform3(index,ref vector3))},
                { typeof(Vector2),new Action<int,Vector2>((index,vector2)=>GL.Uniform2(index,ref vector2))},
                { typeof(int),new Action<int,int>((index,int1)=>GL.Uniform1(index,int1))},
            };
        /// <summary>
        /// 更新一个变量数据
        /// </summary>
        /// <typeparam name="T">更新量的类型</typeparam>
        /// <param name="name">变量名字</param>
        /// <param name="value">取值</param>
        public void UploadUniform<T>(string name, T value)
        {
            int Index = info.GetUniformIndexByInfo(name, value.GetType());
            if (Index < 0)
            {
                return;
            }
            dic[value.GetType()].DynamicInvoke(Index, value);
        }
        /// <summary>
        /// 更新一个变量数据,减少反射作用
        /// </summary>
        /// <typeparam name="T">更新量的类型</typeparam>
        /// <param name="name">变量名字</param>
        /// <param name="value">取值</param>
        /// <param name="type">该值的类型</param>
        public void UploadUniform(string name, object value, Type type)
        {
            int Index = info.GetUniformIndexByInfo(name, type);
            if (Index < 0)
            {
                return;
            }
            dic[type].DynamicInvoke(Index, value);
        }

        /// <summary>
        /// 获取着色器中一个变量的索引,建议只在构建ShaderInfo中使用
        /// </summary>
        /// <param name="name">变量名称</param>
        /// <returns>索引值</returns>
        public int GetUniformIndex(string name)
        {
            Use();
            int index = GL.GetUniformLocation(Handle, name);
            if (index == -1)
            {
                Log.LogOut("未能查找到Uniform：" + name + "于着色器:\n", "Uniform:" + name);
            }
            return index;
        }

        /// <summary>
        /// 根据着色器信息设置采样器的绑定单元
        /// </summary>
        /// <param name="shader">目标着色器</param>
        private void SetSamplerUnitByInfo()
        {
            foreach (var uinfo in info.uniformInfos)
            {
                if (uinfo.type == typeof(Texture2D) || uinfo.type == typeof(TextureBox))
                {
                    SetSampler(uinfo.name, uinfo.index);
                }
            }
        }
        /// <summary>
        /// 设置一个纹理采样器的对应单元
        /// </summary>
        /// <param name="name">纹理名称</param>
        /// <param name="unit">纹理单元</param>
        private void SetSampler(string name, int unit)
        {
            int index = GetUniformIndex(name);
            if (index != -1)
                GL.Uniform1(index, unit);
            else
                throw new Exception("索引失败，图像纹理单元绑定失败");
        }

        //过去的uniform手动提交数据更改 现在交由Material自动执行
        //public void UploadUniformMat4(string name, Matrix4 value)
        //{
        //    int index = FindUniformIndex(name);
        //    GL.UniformMatrix4(index, true, ref value);
        //}
        //private int FindUniformIndex(string name)
        //{
        //    int index = GL.GetUniformLocation(Handle, name);
        //    if (index == -1)
        //    {
        //        Log.LogOut("Uniform索引异常,Class:" + this.ToString() + ",name:" + name, "Uniform:" + name);
        //    }
        //    return index;
        //}

        /// <summary>
        /// 绑定对应的变量块到绑定点
        /// </summary>
        /// <param name="name">变量块名称</param>
        /// <param name="point">绑定点</param>
        public void BindUniformToPoint(string name, int point)
        {
            //int index = GL.GetProgramResourceIndex(Handle, ProgramInterface.UniformBlock, name);
            int index = GL.GetUniformBlockIndex(Handle, name);
            if (index == -1)
            {
                Log.LogOut("UniformBlock索引异常,Class:" + this.ToString() + ",name:" + name, "UniformBlock:" + name);
            }
            GL.UniformBlockBinding(Handle, index, point);

            //int uniforms;
            //GL.GetActiveUniformBlock(Handle, index, ActiveUniformBlockParameter.UniformBlockActiveUniforms, out uniforms);
            //var indices = new int[uniforms];
            //GL.GetActiveUniformBlock(Handle, index, ActiveUniformBlockParameter.UniformBlockActiveUniformIndices, indices);
            //int[] offsets = new int[uniforms];
            //GL.GetActiveUniforms(Handle, uniforms, indices, ActiveUniformParameter.UniformOffset, offsets);
            //Console.WriteLine(offsets);

        }

        ~Shader()
        {
            GL.DeleteProgram(Handle);
        }

        public void Dispose()
        {
            GL.DeleteProgram(Handle);
            ShaderManager.Delete(this);
            GC.SuppressFinalize(this);
        }
    }
}
